package cli.tools;

/**
 * BudgetBuoy
 * 
 * Simplify your personal expense management
 * 
 * @team cs2103jan13-w15-3j
 */

//--------------------------------------------------------------

/**
 * Action Class
 * 
 * Description:Contains methods like undo(), redo() and addToUndoStack()
 *             It performs the undo and redo action by using 2 stacks,
 *             the undoStack and the redoStack.
 * Undo/Redo
 * -Unexecute (undo) the last action they just performed
 * -Re-execute (redo) the last undone action
 *			
 * @package cli.tools
 * @author  Tan Si Ling (A0085314J)
 * @editor(s)  -
 */

import java.util.*;
import cli.*;
import cli.menu.Function;
import logic.API;
import shared.*;


public class Action extends Function{

	static Stack<UndoPair> undoStack;
	static Stack<UndoPair> redoStack;
	private UndoPair tempUndo;
	private UndoPair tempRedo;
	private ArrayList<Category> list;
	protected API api;

	//constructor
	public Action(API a){
		undoStack = new Stack<UndoPair>();
		redoStack = new Stack<UndoPair>();
		api= a;

	}


	//Precondition: -the undoStack is not empty
	//              -the all the actions performed in the cli have
	//               called addToUndoStack, before calling undo().
	//Postcondition: Undo the previous action done by user
	public void undo() throws Exception{


		if(undoStack.empty()){  //if undoStack is empty print out an error msg
			cli.println("*** Undo error : There's nothing to be undone  ***");
			throw new EmptyStackException();
		} 

		tempUndo = undoStack.pop();
		redoStack.push(tempUndo);
		/**
		 * Undo using tempUndo
		 */

		Object tempOld, tempNew, tempChange, tempTypeModified;

		tempOld = tempUndo.getOld();
		tempNew = tempUndo.getNew();
		tempChange = tempUndo.getChange();


		switch(tempUndo.getProcess()){


		/**
		 * *************************************************
		 *    UNDO ADD
		 */

		case ADDINCOME:

			/**
			 *  add income's UndoPair (null, new, "AddIncome") 
			 *	tempNew: Income object
			 */
			api.deleteIncome((Income) tempNew);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



		case ADDEXPENSE:

			/**
			 * add expense's UndoPair (null, new, "AddExpense")
			 * tempNew : Expense Object
			 */

			api.deleteExpense((Expense) tempNew);
			cli.println("Undo " + tempUndo.getProcess()+" completed");

			break;


		case ADDCASHLOAN:

			/**
			 * add CL's UndoPair (null, new, "Add Cash Loan")
			 */	
			api.deleteLoan((Loan) tempNew);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



		case ADDITEMLOAN:

			/**
			 * add IL's UndoPair (null, new, "AddItemLoan")
			 */
			api.deleteLoan((Loan) tempNew);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



		case ADDCATEGORIES:
			// delete away the category , keep the category somewhere
			// list past is an object
			list = api.getCategoryList();
			list.remove((Category) tempNew);

			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



		case ADDPAYMENTMODES:
			api.getModeList().remove((Mode) tempNew);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


		case ADDLOANINSTALLMENT:
			/**
			 * (Object) null, New, "Add Loan Installments",(Object) payment,(Object) loanToPay
			 */
			api.deleteExpense((Expense) tempNew);
			((Expense) tempChange).getLinkToLoan().getInstallments().remove((Expense) tempNew);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


		case ADDFUTUREPAYMENT_LOAN:
			//tempNew: Payment object contains amount and date[].
			((Loan) tempChange).deleteFuturePayments((Payment)tempNew);

			break;


		case ADDRECEIPT:
			api.addReceipt((Receipt) tempNew);
			break;




			/**
			 * ****************************************************
			 *  	UNDO DELETE
			 */

		case DELETEINCOME:

			/**
			 * delete income's UndoPair (old, null, "DeleteIncome")
			 * tempOld: Income object
			 */
			api.addIncome((Income) tempOld);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


		case DELETEEXPENSE:

			/**
			 * delete expense's UndoPair (old, null, "DeleteExpense")
			 * tempOld : Expense Object
			 */

			api.addExpense((Expense) tempOld);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;




		case DELETECASHLOAN:
		case DELETEITEMLOAN:
		case DELETELOAN:

			/**
			 * (old, null, "deleteCashLoan")
			 */

			api.addLoan((Loan) tempOld);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


		case DELETECATEGORIES:

			/**
			 * add the category name
			 */
			list = api.getCategoryList();
			list.add((Category)tempOld);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;

			//ATE up by mergeMode
		case DELETEPAYMENTMODES:
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


		case DELETELOANINSTALLMENTS:
			api.addExpense((Expense) tempOld);
			((Expense)tempOld).getLinkToLoan().getInstallments().add((Expense) tempOld);
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



			/**
			 * *************************************************
			 *    UNDO MERGE
			 */

		case MERGECATEGORIES:

			//UNMERGE CATEGORY
			//tempOld-> Category from, tempNew-> Category to, tempChange-> Record List 

			for(int i=0; i<((ArrayList<Record>)tempChange).size(); i++){

				if( ((ArrayList<Record>)tempChange).get(i).getClass() == Expense.class){
					Expense tempExp = (Expense) ((ArrayList<Record>)tempChange).get(i);
					tempExp.modifyCat((Category)tempOld);
				}

				else if(((ArrayList<Record>)tempChange).get(i).getClass() == Loan.class){
					Loan tempLoan = (Loan) ((ArrayList<Record>)tempChange).get(i); 
					tempLoan.modifyCategory((Category)tempOld);
				}
			}


			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;

		case MERGEMODES:

			//tempOld-> Mode from, tempNew-> Mode to, tempChange-> Expense List 

			for(int i=0; i<((ArrayList<Record>)tempChange).size(); i++){
				Expense tempExp = (Expense) ((ArrayList<Record>)tempChange).get(i);
				tempExp.modifyMode((Mode)tempOld);
			}


			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



			//check if needed	
		case MERGEPAYMENTMODES:
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



			/**
			 * *************************************************
			 *    UNDO SETTINGS
			 */

		case ALERTSETTINGS:
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;




			/**
			 * *************************************************
			 *    UNDO MODIFY
			 *    
			 *    //TempOld : Records, Amount   
			 */

			//modify the category of Loan itself
		case MODIFYCATEGORY:
			((Loan)tempChange).modifyCategory((Category) tempOld);
			break;


		case MODIFYINCOMENAME:
			api.modifyIncomeName((Income)tempChange,(String)tempOld);
			break;

		case MODIFYEXPENSENAME: 
			api.modifyExpenseName((Expense) tempChange,(String) tempOld);
			break;


			// modify name of Loan, category,payment modes(payment name is payment mode)
		case MODIFYLOANNAME:
			api.modifyName((Record)tempChange,(String)tempOld);
			break;
		case MODIFYCATEGORYNAME:
			api.modifyCategory((Category)tempChange,(String)tempOld);
			break;

		case MODIFYPAYMENTMODENAME:
			api.modifyMode((Mode)tempChange,(String)tempOld);
			break;



		case MODIFYINCOMEAMOUNT:
			api.modifyIncomeAmount((Income)tempChange, (Integer)tempOld);
			break;
		case MODIFYEXPENSEAMOUNT:
			api.modifyExpenseAmount((Expense)tempChange, (Integer)tempOld);
			break;

		case MODIFYLOANAMOUNT:
			api.modifyLoanAmount((Loan) tempChange, (Integer)tempOld);
			break;

			// ###case for Modify PAYMENT amount
			//cli.println("Undo " + tempUndo.getProcess()+" completed");


			//action.addToUndoStack((Object)subject,(Object) incomeS,Process.MODIFYDATE,(Object) a, (Object)"INCOME");


		case MODIFYINCOMEDATE:
			api.modifyIncomeDate((Income) tempChange, ((int[]) tempOld)[0], ((int[])tempOld)[1],((int[])tempOld)[2]);
			break;


		case MODIFYEXPENSEDATE:
			api.modifyExpenseDate((Expense) tempChange, ((int[]) tempOld)[0], ((int[])tempOld)[1],((int[])tempOld)[2]);
			break;

		case MODIFYLOANDATE:
			api.modifyDate((Loan) tempChange,((int[]) tempOld)[0], ((int[])tempOld)[1],((int[])tempOld)[2]);
			break;

			//cli.println("Undo " + tempUndo.getProcess()+" completed");




		case MODIFYDESCRIPTION:  // modify description for Income, Expense and Loan type

			api.modifyDescription((Record) tempChange, (String)tempOld);

			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


			//ROLE MODEL FOR MODIFYING THINGS
		case MODIFYFUTUREPAYMENTAMOUNT:
			((Payment)tempChange).modifyAmt((Integer)tempOld);
			break;

		case MODIFYFUTUREPAYMENTDATE:

			((Payment)tempChange).modifyDay(((int[])tempOld)[0]);
			((Payment)tempChange).modifyDay(((int[])tempOld)[1]);
			((Payment)tempChange).modifyDay(((int[])tempOld)[2]);

			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;
			//END ROLE MODEL
			//EVERYTHING ELSE IS WRONG


		case MODIFYEXPENSEPAYMENTMODES:
			//tempOld-> Mode old, tempNew-> Mode new, tempChange->Expense object

			((Expense)tempChange).modifyMode((Mode)tempOld);

			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



			/**
			 * *************************************************
			 *    UNDO CLEAR
			 */

			//TO DO => change the loan's clear() boolean
			//tempOld -> null, tempNew-> null, tempChange-> Loan ,type-> null 

			//##Check if it's right	
		case CLEARLOAN:

			if(((Loan)tempChange).isClear()== true)
				((Loan)tempChange).unclear();
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;

		case UNCLEARLOAN:

			if(((Loan)tempChange).isClear()== false)
				((Loan)tempChange).clear();

			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;


			/**
			 * *************************************************
			 *   END OF UNDO
			 */

		}	
	}



	//Precondition: -the redoStack is not empty
	//              - undo() method is called first before redo()
	//Postcondition: Redo the previous action done by user
	public void redo() throws Exception{

		if(redoStack.empty()){  //if redoStack is empty print out an error msg
			cli.println("*** Redo error : There's nothing to be re-done  ***");
			throw new EmptyStackException();
		}	

		tempRedo = redoStack.pop();
		undoStack.push(tempRedo); //to undo and redo infinite number of times

		/**
		 * Redo Using the tempRedo
		 */	
		Object tempOld, tempNew, tempChange;

		tempOld = tempRedo.getOld();
		tempNew = tempRedo.getNew();
		tempChange = tempUndo.getChange();


		switch(tempRedo.getProcess()){

		/**
		 * *************************************************
		 *    REDO ADD
		 */

		case ADDINCOME:	

			api.addIncome((Income)tempNew);

			break;

		case ADDEXPENSE:

			api.addExpense((Expense)tempNew);

			break;

		case ADDCASHLOAN:

			api.addLoan((Loan)tempNew);

			break;

		case ADDITEMLOAN:

			api.addLoan((Loan)tempNew);
			break;


		case ADDCATEGORIES:

			ArrayList<Category> list = api.getCategoryList();
			list.add((Category) tempNew);
			break;

		case ADDPAYMENTMODES:

			api.getModeList().add((Mode)tempNew);

			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;


		case ADDLOANINSTALLMENT:
			/**
			 * (Object) null, New, "Add Loan Installments",(Object) payment,(Object) loanToPay
			 */
			api.addExpense((Expense)tempNew);
			((Expense)tempChange).getLinkToLoan().getInstallments().add((Expense)tempNew);
			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;

		case ADDFUTUREPAYMENT_LOAN:

			//tempNew: Payment object contains amount and date[].

			((Loan) tempChange).addFuturePayments((Payment)tempNew);

			break;

		case ADDRECEIPT:
			//delete receipt
			api.delete((Receipt) tempNew);
			break;


			/**
			 * *************************************************
			 *    REDO DELETE
			 */

		case DELETEINCOME:

			api.deleteIncome((Income)tempNew);
			break;


		case DELETEEXPENSE:

			api.deleteExpense((Expense)tempNew);
			break;


		case DELETECASHLOAN:
		case DELETEITEMLOAN:
		case DELETELOAN:

			api.deleteLoan((Loan)tempNew);
			break;

		case DELETECATEGORIES:


			/**
			 * delete the category name
			 */
			list = api.getCategoryList();
			list.remove((Category)tempOld);

			break;



		case DELETEPAYMENTMODES:
			api.getModeList().remove((Mode)tempOld);
			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;


		case DELETELOANINSTALLMENTS:
			api.deleteExpense((Expense) tempOld);
			((Expense) tempOld).getLinkToLoan().getInstallments().remove(((Expense) tempOld));
			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;

			/**
			 * *************************************************
			 *    REDO MERGE
			 */

		case MERGECATEGORIES:

			/**
			 * (merge1, merge2, "MergeCategories")
			 * merge1 and merge2 are categories
			 * need the list of things
			 * //tempOld->From , tempNew-> To
			 * //api.mergeCategory(tempOld, tempNew);
			 */
			api.mergeCategory((Category)tempOld,(Category) tempChange);
			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;

		case MERGEMODES:
			api.mergeMode((Mode)tempOld,(Mode) tempChange);

			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;


			//check if need
		case MERGEPAYMENTMODES:
			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;



			/**
			 * *************************************************
			 *    REDO SETTINGS
			 */

		case ALERTSETTINGS:
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;



			/**
			 * *************************************************
			 *    REDO MODIFY
			 */

			//modify the category of Loan itself
		case MODIFYCATEGORY:
			((Loan)tempChange).modifyCategory((Category) tempNew);
			break;


		case MODIFYINCOMENAME:
			//take the new income object and modify it's name
			api.modifyIncomeName((Income) tempChange,(String) tempNew);
			break;

		case MODIFYEXPENSENAME:
			//take the new expense object and modify it's name
			api.modifyExpenseName((Expense) tempChange,(String) tempNew);
			break;


			// modify name of Loan, category,payment modes(payment name is payment mode)
		case MODIFYLOANNAME:
			api.modifyName((Record)tempChange,(String)tempNew);
			break;
		case MODIFYCATEGORYNAME:
			api.modifyCategory((Category)tempChange,(String)tempNew);
			break;

		case MODIFYPAYMENTMODENAME:
			api.modifyMode((Mode)tempChange,(String)tempNew);
			break;



		case MODIFYINCOMEAMOUNT:
			api.modifyIncomeAmount((Income)tempChange, (Integer)tempNew);
			break;

		case MODIFYEXPENSEAMOUNT:
			api.modifyExpenseAmount((Expense)tempChange, (Integer)tempNew);	
			break;

		case MODIFYLOANAMOUNT:
			api.modifyLoanAmount((Loan) tempChange, (Integer)tempNew);
			break;

			//cli.println("Redo " + tempUndo.getProcess()+" completed");



		case MODIFYINCOMEDATE:
			api.modifyIncomeDate((Income)tempChange, ((int[])tempNew)[0], ((int[])tempNew)[1],((int[])tempOld)[2]);
			break;
		case MODIFYEXPENSEDATE: 
			api.modifyExpenseDate((Expense)tempChange, ((int[])tempNew)[0], ((int[])tempNew)[1],((int[])tempOld)[2]);
			break;

		case MODIFYLOANDATE: 
			api.modifyDate((Record)tempChange, ((int[])tempNew)[0], ((int[])tempNew)[1],((int[])tempOld)[2]);
			break;
			//cli.println("Redo " + tempUndo.getProcess()+" completed");



			// modify the description of income, expense and loan type
		case MODIFYDESCRIPTION:
			api.modifyDescription((Record) tempChange, (String)tempNew);

			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;

		case MODIFYFUTUREPAYMENTAMOUNT:
			((Payment)tempChange).modifyAmt((Integer)tempNew);
			break;

		case MODIFYFUTUREPAYMENTDATE:

			/**
			 * For this we do it in a different way:
			 * 
			 * (tempOld, tempNew, MODIFYFUTUREPAYMENTDATE, tempChange, type) would be
			 * 
			 * we get the Payment object from tempOld's paymentDateList
			 * (Payment old, Payment new, MODIFYFUTUREPAYMENTDATE, Loan object (LoanNew), "Loan")
			 */

			((Payment)tempChange).modifyDay(((int[])tempNew)[0]);
			((Payment)tempChange).modifyDay(((int[])tempNew)[1]);
			((Payment)tempChange).modifyDay(((int[])tempNew)[2]);

			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;



		case MODIFYEXPENSEPAYMENTMODES:

			((Expense)tempChange).modifyMode((Mode)tempNew);

			cli.println("Redo " + tempUndo.getProcess()+" completed");
			break;


			/**
			 * *************************************************
			 *    REDO CLEAR
			 */

			//tempOld -> null, tempNew-> null, tempChange-> Loan ,type-> null 

		case CLEARLOAN:

			if(((Loan)tempChange).isClear()== false)
				((Loan)tempChange).clear();
			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;

		case UNCLEARLOAN:

			if(((Loan)tempChange).isClear()== true)
				((Loan)tempChange).unclear();

			cli.println("Undo " + tempUndo.getProcess()+" completed");
			break;

			/**
			 * *************************************************
			 * 	   END OF REDO FUNCTION
			 */

		}
	}


	//Precondition: Objects old, new and change cannot be all null
	//Postcondition: the action's UndoPair is added to the undoStack
	public void addToUndoStack(Object Old, Object New, Process process, Object change){

		UndoPair undo = new UndoPair(Old, New, process, change);
		undoStack.push(undo);

	}
}
